# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 黄文良 <2879625666@qq.com>
# +-------------------------------------------------------------------

# ------------------------------
# API-Demo of Python
# ------------------------------
import time, hashlib, sys, os, json
import urllib.request
import urllib.parse as urlparse

# 添加日志模块
import logging
logger = logging.getLogger(__name__)


class Bt_api():

    # 如果希望多台面板，可以在实例化对象时，将面板地址与密钥传入
    def __init__(self, bt_panel, bt_key):
        if bt_panel:
            self.__BT_PANEL = bt_panel
            self.__BT_KEY = bt_key

    # 获取面板信息
    def GetNetWork(self):
        # 拼接URL地址
        url = self.__BT_PANEL + '/system?action=GetNetWork'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 查看文件列表
    def GetDir(self, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=GetDir'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['path'] = path

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        r_data = json.loads(result)
        
        

        return r_data

    # 查看文件内容，查看伪静态内容
    def GetFileBody(self, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=GetFileBody'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['path'] = path

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 保存文件

    def SaveFileBody(self, data, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=SaveFileBody'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['data'] = data
        p_data['encoding'] = 'utf-8'
        p_data['path'] = path
       

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 批量删除文件
    def SetBatchData(self, data,typenum,path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=SetBatchData'
        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['data'] = data
        p_data['type'] = typenum
        p_data['path'] = path
    

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 删除文件

    def DeleteFile(self, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=DeleteFile'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['path'] = path
      

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 上传文件
    def UploadFile(self, local_file_path, target_path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=UploadFile&path=' + target_path + '&codeing=utf-8'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        
        # 使用requests库发送文件上传请求
        import requests
        try:
            with open(local_file_path, 'rb') as f:
                files = {'zunfile': (os.path.basename(local_file_path), f)}
                # 添加其他必要的参数
                data = p_data
                response = requests.post(url, data=data, files=files, timeout=300)
                return response.json()
        except Exception as e:
            return {'status': False, 'msg': f'文件上传失败: {str(e)}'}

    # 直接上传文件（参考PHP项目的实现）
    def UploadFileDirect(self, file_path, target_path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=UploadFile&path=' + target_path + '&codeing=utf-8'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        
        # 使用urllib发送文件上传请求
        import urllib.request
        import urllib.parse
        
        try:
            # 创建多部分表单数据
            boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
            body = bytearray()
            
            # 添加签名数据
            for key, value in p_data.items():
                body.extend(f'--{boundary}\r\n'.encode())
                body.extend(f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode())
                body.extend(f'{value}\r\n'.encode())
            
            # 添加文件数据 (参考PHP中的zunfile字段)
            file_name = os.path.basename(file_path)
            body.extend(f'--{boundary}\r\n'.encode())
            body.extend(f'Content-Disposition: form-data; name="zunfile"; filename="{file_name}"\r\n'.encode())
            body.extend(f'Content-Type: application/octet-stream\r\n\r\n'.encode())
            
            # 读取文件内容
            with open(file_path, 'rb') as f:
                body.extend(f.read())
            
            body.extend(f'\r\n--{boundary}--\r\n'.encode())
            
            # 创建请求
            req = urllib.request.Request(url)
            req.add_header('Content-Type', f'multipart/form-data; boundary={boundary}')
            req.data = bytes(body)
            
            # 发送请求
            cookie_file = './cookie/cookie' + self.__get_md5(self.__BT_PANEL) + '.cookie'
            import http.cookiejar
            cookie_obj = http.cookiejar.MozillaCookieJar(cookie_file)
            if os.path.exists(cookie_file):
                cookie_obj.load(cookie_file, ignore_discard=True, ignore_expires=True)
            handler = urllib.request.HTTPCookieProcessor(cookie_obj)
            opener = urllib.request.build_opener(handler)
            response = opener.open(req, timeout=300)
            cookie_obj.save(ignore_discard=True, ignore_expires=True)
            result = response.read()
            if type(result) == bytes:
                result = result.decode('utf-8')
            
            # 解析JSON数据
            return json.loads(result)
        except Exception as e:
            return {'status': False, 'msg': f'文件上传失败: {str(e)}'}

    # 分片上传文件检查
    def upload_file_exists(self, filename):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=upload_file_exists'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['filename'] = filename

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 分片上传文件
    def upload_chunk(self, f_path, f_name, f_size, f_start, blob_file_path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=upload'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['f_path'] = f_path
        p_data['f_name'] = f_name
        p_data['f_size'] = str(f_size)
        p_data['f_start'] = str(f_start)
        
        # 使用urllib发送文件上传请求（保持与项目其他API一致的方式）
        import urllib.request
        import urllib.parse
        import mimetypes
        
        try:
            # 创建多部分表单数据
            boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'
            body = bytearray()
            
            # 添加签名数据
            for key, value in p_data.items():
                body.extend(f'--{boundary}\r\n'.encode())
                body.extend(f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode())
                body.extend(f'{value}\r\n'.encode())
            
            # 添加文件数据
            body.extend(f'--{boundary}\r\n'.encode())
            body.extend(f'Content-Disposition: form-data; name="blob"; filename="{f_name}"\r\n'.encode())
            body.extend(f'Content-Type: application/octet-stream\r\n\r\n'.encode())
            
            # 读取文件内容
            with open(blob_file_path, 'rb') as f:
                body.extend(f.read())
            
            body.extend(f'\r\n--{boundary}--\r\n'.encode())
            
            # 创建请求
            req = urllib.request.Request(url)
            req.add_header('Content-Type', f'multipart/form-data; boundary={boundary}')
            req.data = bytes(body)
            
            # 发送请求
            cookie_file = './cookie/cookie' + self.__get_md5(self.__BT_PANEL) + '.cookie'
            import http.cookiejar
            cookie_obj = http.cookiejar.MozillaCookieJar(cookie_file)
            if os.path.exists(cookie_file):
                cookie_obj.load(cookie_file, ignore_discard=True, ignore_expires=True)
            handler = urllib.request.HTTPCookieProcessor(cookie_obj)
            opener = urllib.request.build_opener(handler)
            response = opener.open(req, timeout=300)
            cookie_obj.save(ignore_discard=True, ignore_expires=True)
            result = response.read()
            if type(result) == bytes:
                result = result.decode('utf-8')
            
            # 解析JSON数据
            return json.loads(result)
        except Exception as e:
            return {'status': False, 'msg': f'分片上传失败: {str(e)}'}

    # 新建目录
    def CreateDir(self, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=CreateDir'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['path'] = path

        # p_data['st_mtime'] = int(time.time())

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 新建文件
    def CreateFile(self, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=CreateFile'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['path'] = path

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 剪切文件
    def MvFile(self, sfile, dfile):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=MvFile'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['sfile'] = sfile
        p_data['dfile'] = dfile
        p_data['rename'] = 'true'

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)
    
     # 复制文件
    def CopyFile(self, sfile, dfile):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=CopyFile'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['sfile'] = sfile
        p_data['dfile'] = dfile

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 绑定域名
    def AddDomain(self, domain, webname, id_id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=AddDomain'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['domain'] = domain
        p_data['webname'] = webname
        p_data['id'] = id_id

        # p_data['st_mtime'] = int(time.time())

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        # 解析JSON数据
        return json.loads(result)

    # 查看域名
    def getData_dns(self, domain_id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/data?action=getData'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['table'] = 'domain'
        p_data['list'] = 'True'
        p_data['search'] = domain_id

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
      

        # 解析JSON数据
        return json.loads(result)

    # 删除域名
    def DelDomain(self, id, webname, domain, port):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=DelDomain'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['id'] = id
        p_data['webname'] = webname
        p_data['domain'] = domain
        p_data['port'] = port

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
       

        # 解析JSON数据
        return json.loads(result)

    # 已绑定域名获取
    def getData(self, search):
        # 拼接URL地址
        url = self.__BT_PANEL + '/data?action=getData'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['able'] = 'domain'
        p_data['list'] = True
        p_data['search'] = search

        # p_data['st_mtime'] = int(time.time())

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        

        # 解析JSON数据
        return json.loads(result)

    # 获取子目录
    def GetDirBinding(self, id_id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetDirBinding'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['id'] = id_id

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 绑定子目录域名
    def AddDirBinding(self, id_id, domain, dirName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=AddDirBinding'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['id'] = id_id
        p_data['domain'] = domain
        p_data['dirName'] = dirName

        # p_data['st_mtime'] = int(time.time())

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        

        # 解析JSON数据
        return json.loads(result)

    # 获取伪静态目录
    def GetRewriteList(self, siteName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetRewriteList'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['siteName'] = siteName

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)


    # 压缩文件
    def Zip(self, sfile, dfile,z_type, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=Zip'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['sfile'] = sfile
        p_data['dfile'] = dfile
        p_data['z_type'] = z_type
        p_data['path'] = path
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 解压文件
    def UnZip(self, sfile, dfile):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=UnZip'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['sfile'] = sfile
        p_data['dfile'] = dfile
        p_data['coding'] = 'UTF-8'
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 创建下载外链
    def create_download_url(self, filename, ps='', password='', expire=24):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=create_download_url'
        
        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['filename'] = filename
        p_data['ps'] = ps
        p_data['password'] = password
        p_data['expire'] = expire
        
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        
        # 解析JSON数据
        return json.loads(result)
    
    # 获取下载外链信息
    def get_download_url_find(self, url_id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=get_download_url_find'
        
        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['id'] = url_id
        
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        
        # 解析JSON数据
        return json.loads(result)
    
    # 删除下载外链
    def remove_download_url(self, url_id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=remove_download_url'
        
        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['id'] = url_id
        
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        
        # 解析JSON数据
        return json.loads(result)
    
    # 直接下载文件
    def DownloadFile(self, path):
        '''
        下载文件到本地
        :param path: 文件路径
        '''
        import os
        import urllib.parse
        
        # 添加调试信息
        logger.debug(f"DownloadFile called with path: {path}")
        
        # 规范化路径
        try:
            normalized_path = os.path.normpath(path)
            logger.debug(f"Normalized path: {normalized_path}")
        except Exception as e:
            return {
                'status': False,
                'msg': '文件路径不合法'
            }
        
        # 检查路径是否包含危险字符
        logger.debug(f"Checking for directory traversal: {'..' in normalized_path}")
        if '..' in normalized_path:
            return {
                'status': False,
                'msg': '文件路径不合法'
            }
        
        # 检查路径是否以斜杠开头（相对路径）
        # 但允许以网站根目录开头的路径（如/www/wwwroot/）
        logger.debug(f"Checking for absolute path: {normalized_path.startswith('/')}")
        if normalized_path.startswith('/') and not normalized_path.startswith('/www/wwwroot/'):
            return {
                'status': False,
                'msg': '文件路径不合法'
            }
        
        # 检查路径长度
        logger.debug(f"Checking path length: {len(normalized_path)}")
        if len(normalized_path) > 1000:
            return {
                'status': False,
                'msg': '文件路径过长'
            }
        
        # 将路径中的反斜杠替换为正斜杠，以确保在URL中正确处理
        normalized_path = normalized_path.replace('\\', '/')
        logger.debug(f"Final normalized path: {normalized_path}")
        
        # 不再使用GetFileBody检查文件是否存在，因为对于二进制文件会返回错误
        # 直接尝试创建下载外链，让宝塔面板自己处理文件存在性检查
        
        # 文件存在，直接创建下载外链
        filename = os.path.basename(normalized_path)
        download_info = self.create_download_url(normalized_path, filename)
        
        # 检查外链是否创建成功
        if download_info.get('status') == True and 'data' in download_info:
            # 构造下载URL
            download_url = f"{self.__BT_PANEL}/down/{download_info['data']['token']}"
            
            return {
                'status': True,
                'download_url': download_url,
                'filename': filename,
                'url_id': download_info['data']['id']  # 保存外链ID，用于后续删除
            }
        else:
            # 特殊情况处理：宝塔API有时会返回status=false但实际已经创建了外链
            # 在这种情况下，msg字段会包含外链信息
            download_msg = download_info.get('msg', {})
            if isinstance(download_msg, dict) and 'token' in download_msg:
                # 构造下载URL
                download_url = f"{self.__BT_PANEL}/down/{download_msg['token']}"
                
                return {
                    'status': True,  # 改为True，因为实际上已经成功创建了外链
                    'download_url': download_url,
                    'filename': filename,
                    'url_id': download_msg.get('id', 0)  # 尝试获取外链ID
                }
            
            # 如果是因为外链已存在导致创建失败，尝试获取已存在的外链信息
            if '已经' in str(download_info.get('msg', '')) and '分享过了' in str(download_info.get('msg', '')):
                # 当外链已存在时，尝试获取已存在的外链列表，找到匹配的外链
                try:
                    # 获取外链列表
                    url_list = self.get_download_url_list()
                    if url_list.get('status', False) and 'data' in url_list:
                        # 查找匹配的外链
                        for item in url_list['data']:
                            # 检查文件路径是否匹配
                            if 'filename' in item and item['filename'] == normalized_path:
                                # 找到匹配的外链，返回正确的信息
                                download_url = f"{self.__BT_PANEL}/down/{item['token']}"
                                return {
                                    'status': True,
                                    'download_url': download_url,
                                    'filename': filename,
                                    'url_id': item['id']  # 返回真实的外链ID
                                }
                            # 有时文件路径可能不完全匹配，尝试通过token匹配
                            elif 'token' in download_info.get('msg', {}) and 'token' in item and item['token'] == download_info['msg']['token']:
                                # 找到匹配的外链，返回正确的信息
                                download_url = f"{self.__BT_PANEL}/down/{item['token']}"
                                return {
                                    'status': True,
                                    'download_url': download_url,
                                    'filename': filename,
                                    'url_id': item['id']  # 返回真实的外链ID
                                }
                except Exception as e:
                    # 获取外链列表失败，继续使用原来的逻辑
                    pass
                
                # 如果获取已存在的外链失败，但msg中包含token信息，使用msg中的信息
                if isinstance(download_info.get('msg'), dict) and 'token' in download_info['msg']:
                    download_msg = download_info['msg']
                    download_url = f"{self.__BT_PANEL}/down/{download_msg['token']}"
                    return {
                        'status': True,
                        'download_url': download_url,
                        'filename': filename,
                        'url_id': download_msg.get('id', 0)  # 尝试使用msg中的ID
                    }
                
                # 如果以上方法都失败，使用原来的模拟方式
                import hashlib
                import time
                # 使用文件路径和时间戳生成一个token（模拟宝塔的行为）
                token_input = normalized_path + str(int(time.time()))
                token = hashlib.md5(token_input.encode('utf-8')).hexdigest()[0:12]
                
                # 构造下载URL
                download_url = f"{self.__BT_PANEL}/down/{token}"
                
                return {
                    'status': True,
                    'download_url': download_url,
                    'filename': filename,
                    'url_id': 0  # 使用0作为ID，表示这是一个模拟的外链
                }
            
            # 如果是文件不存在的错误，返回相应的错误信息
            if '不存在' in str(download_info.get('msg', '')) or 'not found' in str(download_info.get('msg', '')).lower():
                return {
                    'status': False,
                    'msg': '文件不存在'
                }
            
            # 如果获取已存在的外链也失败，返回错误信息
            return {
                'status': False,
                'msg': download_info.get('msg', '创建下载链接失败，请稍后再试')
            }
    
    # 获取下载链接列表
    def get_download_url_list(self):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=get_download_url_list'
        
        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        
        # 解析JSON数据
        return json.loads(result)
    
    # 获取当前运行目录
    def GetDirUserINI(self, id, path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetDirUserINI'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['id'] = id

        p_data['path'] = path
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 设置当前运行目录
    def SetSiteRunPath(self, id, runPath):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=SetSiteRunPath'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['id'] = id

        p_data['runPath'] = runPath
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 获取当前ssl状态
    def GetSSL(self, siteName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetSSL'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['siteName'] = siteName
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 保存ssl

    def SetSSL(self, siteName, key, csr):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=SetSSL'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['type'] = 1
        p_data['siteName'] = siteName
        p_data['key'] = key
        p_data['csr'] = csr
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 续签ssl

    def renew_cert(self, index):
        # 拼接URL地址
        url = self.__BT_PANEL + '/acme?action=renew_cert'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['index'] = index

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 申请ssl时获取域名
    def GetSiteDomains(self, id_id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetSiteDomains'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['id'] = id_id

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 申请ssl
    def apply_cert_api(self, domain, id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/acme?action=apply_cert_api'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['domains'] = domain
        p_data['auth_type'] = 'http'
        p_data['auth_to'] = id
        p_data['auto_wildcard'] = '0'
        p_data['id'] = id

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)
     # ssl申请状态
    def get_lines(self):
        # 拼接URL地址
        url = self.__BT_PANEL + '/ajax?action=get_lines'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['num'] = 10
        p_data['filename: '] = '/www/server/panel/logs/letsencrypt.log'

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 关闭ssl
    def CloseSSLConf(self, siteName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=CloseSSLConf'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['updateOf'] = '1'
        p_data['siteName'] = siteName

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 打开强制https
    def HttpToHttps(self, siteName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=HttpToHttps'
        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['siteName'] = siteName

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 关闭强制https
    def CloseToHttps(self, siteName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=CloseToHttps'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['siteName'] = siteName

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 查看默认文档
    def GetIndex(self, id):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetIndex'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['id'] = id

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 设置默认文档
    def SetIndex(self, id, Index):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=SetIndex'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['id'] = id
        p_data['Index'] = Index

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 获取当前php版本
    def GetSitePHPVersion(self,siteName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetSitePHPVersion'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['siteName'] = siteName
        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 获取php版本
    def GetPHPVersion(self):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetPHPVersion'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 设置php版本
    def SetPHPVersion(self, sitename, version):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=SetPHPVersion'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['siteName'] = sitename
        p_data['version'] = version
        p_data['other'] = ''

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)
    # 日志
    def GetSiteLogs(self, sitename):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetSiteLogs'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['siteName'] = sitename
      

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)
    
    
     # 网站操作日志
    def webgetData(self, search, page=1, limit=10):
        # 拼接URL地址
        url = self.__BT_PANEL + '/logs/panel/get_logs_bytype'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['keywords'] = ''
        p_data['p'] = page
        p_data['limit'] = limit
        p_data['stype'] = '网站管理'
        p_data['search'] = search
        
        # 添加调试信息
        logger.info(f"webgetData called with: search={search}, page={page}, limit={limit}")
        logger.info(f"Sending data to API: {p_data}")

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        
        # 添加调试信息
        logger.info(f"API response received, length: {len(result) if result else 0}")
        # 只记录响应的前200个字符以避免日志过大
        logger.info(f"API response preview: {result[:200] if result else 'None'}")

        # 解析JSON数据
        try:
            parsed_result = json.loads(result)
            logger.info(f"Parsed result keys: {parsed_result.keys() if isinstance(parsed_result, dict) else 'Not a dict'}")
            return parsed_result
        except json.JSONDecodeError as e:
            logger.error(f"Failed to parse JSON response: {e}")
            logger.error(f"Raw response: {result}")
            # 返回原始响应以便调试
            return {'error': 'Failed to parse response', 'raw_response': result}
    
      # 获取nginx状态
    def get_soft_find(self,sName):
        # 拼接URL地址
        url = self.__BT_PANEL + '/plugin?action=get_soft_find'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['sName'] = sName
       
      

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)
    

    # 计算目录大小
    def get_path_size(self,path):
        # 拼接URL地址
        url = self.__BT_PANEL + '/files?action=get_path_size'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['path'] = path
       
      

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)
    

    # 计算数据库大小
    def GetInfo(self, db_name):
        # 拼接URL地址
        url = self.__BT_PANEL + '/database?action=GetInfo'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        p_data['db_name'] = db_name
       

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        return json.loads(result)

    # 获取系统信息
    def get_system_info(self):
        # 拼接URL地址
        url = self.__BT_PANEL + '/system?action=GetSystemTotal'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)

        # 解析JSON数据
        try:
            data = json.loads(result)
            # 返回完整的系统信息
            return data
        except:
            return {'system': 'Unknown', 'version': 'Unknown', 'time': 0}

    # 开放端口
    def open_port(self, port, brief):
        # 拼接URL地址
        url = self.__BT_PANEL + '/firewall/com/set_port_rule'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名

        # 传入参数
        p_data['protocol'] = 'tcp'
        p_data['port'] = port
        p_data['choose'] = 'all'
        p_data['types'] = 'accept'
        p_data['brief'] = brief
        p_data['domain'] = ''
        p_data['chain'] = 'INPUT'
        p_data['operation'] = 'add'
        p_data['strategy'] = 'accept'

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        return json.loads(result)
    
    # 创建反向代理
    def create_proxy(self, action='CreateProxy', proxyname='', proxysite='', todomain='', sitename=''):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=CreateProxy'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['action'] = action
        p_data['proxyname'] = proxyname
        p_data['proxysite'] = proxysite
        p_data['todomain'] = todomain
        p_data['sitename'] = sitename  # 添加网站名称参数
        # 设置默认值
        p_data['subfilter'] = '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]'
        p_data['type'] = '1'
        p_data['cache'] = '0'
        p_data['advanced'] = '0'
        p_data['proxydir'] = '/'
        p_data['cachetime'] = '1'

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        return json.loads(result)
    
    # 获取反向代理列表
    def get_proxy_list(self, sitename):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=GetProxyList'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['sitename'] = sitename

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        return json.loads(result)
    
    # 删除反向代理
    def remove_proxy(self, proxyname, sitename):
        # 拼接URL地址
        url = self.__BT_PANEL + '/site?action=RemoveProxy'

        # 准备POST数据
        p_data = self.__get_key_data()  # 取签名
        p_data['proxyname'] = proxyname
        p_data['sitename'] = sitename

        # 请求面板接口
        result = self.__http_post_cookie(url, p_data)
        return json.loads(result)



    
    # 计算MD5
    def __get_md5(self, s):
        m = hashlib.md5()
        m.update(s.encode('utf-8'))
        return m.hexdigest()

    # 构造带有签名的关联数组
    def __get_key_data(self):
        now_time = int(time.time())
        p_data = {
            'request_token': self.__get_md5(str(now_time) + '' + self.__get_md5(self.__BT_KEY)),
            'request_time': now_time
        }
        return p_data

    # 发送POST请求并保存Cookie
    # @url 被请求的URL地址(必需)
    # @data POST参数，可以是字符串或字典(必需)
    # @timeout 超时时间默认1800秒
    # return string
    def __http_post_cookie(self, url, p_data, timeout=1800):
        cookie_file = './cookie/cookie' + self.__get_md5(self.__BT_PANEL) + '.cookie';

        # Python3
        import urllib.request, ssl, http.cookiejar
        cookie_obj = http.cookiejar.MozillaCookieJar(cookie_file)
        if os.path.exists(cookie_file):
            cookie_obj.load(cookie_file, ignore_discard=True, ignore_expires=True)
        handler = urllib.request.HTTPCookieProcessor(cookie_obj)
        data = urlparse.urlencode(p_data).encode('utf-8')
        req = urllib.request.Request(url, data)
        opener = urllib.request.build_opener(handler)
        try:
            response = opener.open(req, timeout=timeout)
            cookie_obj.save(ignore_discard=True, ignore_expires=True)
            result = response.read()
            if type(result) == bytes: result = result.decode('utf-8')
            
            # 检查是否是IP校验失败的错误
            if 'IP校验失败' in result and '您的访问IP为' in result:
                # 尝试解析为JSON格式，如果失败则构造标准错误格式
                try:
                    import json
                    return json.dumps(json.loads(result))
                except:
                    # 如果不是标准JSON格式，构造标准错误格式
                    return '{"status": false, "msg": "' + result.replace('"', '\\"') + '"}'
            
            return result
        except Exception as e:
            # 确保异常时也返回标准的JSON错误格式
            import json
            return json.dumps({'status': False, 'msg': f'请求失败: {str(e)}'})

if __name__ == '__main__':
    bt = Bt_api('http://1.14.103.227:8888', 'YXb5OfgP2Cplav3xmws4pbMVzjTW3rYv')  
    data=bt.webgetData("aa.com",2,10)
    print(data)
  
    
        
